<?php
/*
 * 	Yamp52 - Yet Another Magical PHP framework
 *	http://code.google.com/p/yamp52/
 *	
 *	Copyright (C) 2009, Krzysztof Drezewski <krzych@krzych.eu>
 *	
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 3 of the License, or
 *	(at your option) any later version.
 *	
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *	GNU General Public License for more details.
 *	
 *	You should have received a copy of the GNU General Public License
 *	along with this program. If not, see <http://www.gnu.org/licenses/>.
 */


/**
 * Security Container is responsible for checking that user is allowed to call conroller/action
 * @package Yamp
 * @author krzych
 *
 */
class SecurityContainer implements Injectable {


	/**
	 * @Service("AuthorizationService")
	 * @var Authorization
	 */
	private $AuthorizationService;

	/**
	 * @Service("Session")
	 * @var Session
	 */
	private $SessionContainer;

	public function __set($name, $value) {
		$this->$name = $value;
	}

	/**
	 *
	 * @param string $class
	 * @param string $method
	 * @return boolean
	 */
	public function isUserAllowedForAction($class, $method) {
		$User = $this->AuthorizationService->getLoggedUser();
		return $this->isAllowed($class, $method, $User);
	}

	/**
	 *
	 * @param string $class
	 * @param string $method
	 * @param User $User
	 * @return boolean
	 */
	public function isAllowed($class, $method, User $User) {
		static $arr;
		static $Roles;

		if(!$class || !$method) {
			return false;
		}

		$Roles = $this->SessionContainer->getRaw('Roles');
		if( empty($Roles) || $Roles->count() == 0 ) {
			$Roles = $User->Roles;
			$this->SessionContainer->saveRaw('Roles',$Roles);
		}

		if(!empty($Roles) && 
			$Roles instanceof Doctrine_Collection && 
				$Roles->count() > 0) {

				foreach($Roles as $Role) {

				$ancestors = $this->SessionContainer->getRaw($Role->name.'_Ancestors');
				if(empty($ancestors)) {
					//we need our parent's Roles and ours
					$ancestors = $Role->getNode()->getAncestors();
					$ancestors[] = $Role;
					$this->SessionContainer->saveRaw($Role->name.'_Ancestors', $ancestors);
				}
				if(!empty($ancestors)) {
					foreach($ancestors as $ancestor) {
						$name = $ancestor->name;
						$arr = $this->SessionContainer->getRaw($name.'_AncestorSecuredObjects');
						//cache as much as you can...
						if(empty($arr[$name])) {
							//toArray is painfull
							$arr[$name] = $ancestor->SecuredObjects->toArray();
							$this->SessionContainer->saveRaw($name.'_AncestorSecuredObjects',$arr);
						}
						if(!empty($arr[$name])) {
							foreach($arr[$name] as $SecuredObject) {
								if($SecuredObject['class'] == $class &&
								$SecuredObject['method'] == $method) {
									return $SecuredObject['allowed'];
								}
							}
						}

					}
				}

			}
		}
		//default is not allowed
		return false;
	}

	/**
	 * check if user or his role has given acl
	 * 
	 * ACLs are not inherited, that means ancestors acls doesn't count.
	 * @return boolean
	 */
	public function hasAcl($aclName) {
		$acls = $this->SessionContainer->getRaw('UserAcl');
		if(!$acls) {
			$User = $this->AuthorizationService->getLoggedUser();
			$acls = $this->fetchUserAcls($User);

			$this->SessionContainer->saveRaw('UserAcl', $acls);
		}
		if(empty($acls)) {
			return false;
		}
		$key = array_search($aclName, $acls);
		if($key !== false) {
			return true;
		}
		return false;
	}

	/**
	 * returns flat array from results given by
	 * fetchRoleAcl and fetchUserAcl
	 * 
	 * @param array $result
	 * @return array
	 */
	private function flattenAclResult($result) {
		$ret = array();
		if(!empty($result['Acls'])) {
			foreach($result['Acls'] as $idx => $row) {
				$ret[] = $row['name'];
			}
		}
		return $ret;
	}
	
	private function fetchRoleAcl(Role $Role) {
		$q = Doctrine_Query::create()
		->select('a.name, r.id')->from('Role r')->leftJoin('r.RoleAcl ra')
		->leftJoin('r.Acls a')->where('r.name = ?', $Role->name)
		->useResultCache(true);
		$result = $q->fetchOne(array(), Doctrine::HYDRATE_ARRAY);
		return $this->flattenAclResult($result);
	}
	
	/**
	 * returns user's acls
	 * @param User $User
	 * @return array
	 */
	private function fetchUserAcls(User $User) {
		$q = Doctrine_Query::create()
		->select('a.id, u.id')->from('User u')->leftJoin('u.UserAcl ua')
		->leftJoin('u.Acls a')->where('u.id = ?', $User->id)
		//we use clever session result cache
		->useResultCache(true);
		$result = $q->fetchOne(array(),Doctrine::HYDRATE_ARRAY);//fetchArray();
		$acls = $this->flattenAclResult($result);

		foreach($User->Roles as $Role) {
			$acls = array_merge($this->fetchRoleAcl($Role), $acls);
		}
		return $acls;
	}

	/**
	 * checks if user has given role
	 * 
	 * Roles are inherited, that means user has all ancestor's roles
	 * 
	 * @return boolean
	 */
	public function hasRole($roleName) {
		static $RolesArray;
		$key = false;

		$RolesArray = $this->SessionContainer->getRaw('RolesArray');
		if(empty($RolesArray)) {
			//we haven't array of user roles yet
			$Roles = $this->AuthorizationService->getLoggedUser()->Roles;
			if(!empty($Roles)) {

				foreach($Roles as $Role) {
					//we need our parent's Roles and ours
					$ancestors = $Role->getNode()->getAncestors();
					if(!$ancestors) {
						$ancestors = new Doctrine_Collection('Role');
					}
					$ancestors->add($Role);

				}
				foreach($ancestors->toArray() as $idx => $Role) {
					$RolesArray[] = $Role['name'];
				}
				$this->SessionContainer->saveRaw('RolesArray', $RolesArray);
			}
		}
		if(!empty($RolesArray)) {
			$key = array_search($roleName, $RolesArray);
			if($key === false) {
				return false;
			} else {
				return true;
			}
		}
		return false;
	}

}